﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Text;

namespace FRS.Devices
{
    public class DeviceBase : Device
    {
        // A delegate type for alive state change.
        public delegate void AliveStateChangedEventHandler(object sender, AliveStateChangedArgs e);
        public event AliveStateChangedEventHandler OnAliveStateChanged;

        private const int TVIF_STATE = 0x8;
        private const int TVIS_STATEIMAGEMASK = 0xF000;
        private const int TV_FIRST = 0x1100;
        private const int TVM_SETITEM = TV_FIRST + 63;

        [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
        private struct TVITEM
        {
            public int mask;
            public IntPtr hItem;
            public int state;
            public int stateMask;
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpszText;
            public int cchTextMax;
            public int iImage;
            public int iSelectedImage;
            public int cChildren;
            public IntPtr lParam;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
                                                 ref TVITEM lParam);

        /// <summary>
        /// Hides the checkbox for the specified node on a TreeView control.
        /// </summary>
        private void HideCheckBox(TreeView tvw, TreeNode node)
        {
            TVITEM tvi = new TVITEM();
            tvi.hItem = node.Handle;
            tvi.mask = TVIF_STATE;
            tvi.stateMask = TVIS_STATEIMAGEMASK;
            tvi.state = 0;
            SendMessage(tvw.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
        }

        void HideCheckboxes(TreeNode node)
        {
            foreach (TreeNode child in node.Nodes)
            {
                HideCheckboxes(child);
            }

            TreeNode output = node as OutputTreeNode;

            if (output == null && node.TreeView != null)
                HideCheckBox(node.TreeView, node);
        }

        public class AliveStateChangedArgs : EventArgs
        {
            public DeviceBase Device { get; private set; }
            public bool AliveState { get; private set; }

            public AliveStateChangedArgs(DeviceBase device, bool aliveState)
            {
                Device = device;
                AliveState = aliveState;
            }
        }

        // //A delegate type for output value change.
        //public delegate void OutputValuesChangedEventHandler(object sender, OutputValuesChangedArgs e);
        //public event OutputValuesChangedEventHandler OnOutputValuesChanged;

        //public class OutputValuesChangedArgs : EventArgs
        //{
        //    public DeviceBase Device { get; private set; }
        //    public int Mask { get; private set; }
        //    public int Value { get; private set; }
            
        //    public OutputValuesChangedArgs(DeviceBase device, int mask, int value)
        //    {
        //        Device = device;
        //        Mask = mask;
        //        Value = value;
        //    }
        //}

        //// A delegate type for output name change.
        //public delegate void OutputNamesChangedEventHandler(object sender, OutputNamesChangedArgs e);
        //public event OutputNamesChangedEventHandler OnOutputNamesChanged;

        //public class OutputNamesChangedArgs : EventArgs
        //{
        //    public DeviceBase Device { get; private set; }
        //    public List<string> OutputNames { get; private set; }

        //    public OutputNamesChangedArgs(DeviceBase device, List<string> outputNames)
        //    {
        //        Device = device;
        //        OutputNames = outputNames;
        //    }
        //}

        private Thread updateThread;
        private bool running;
        private bool waitForRefresh = false;
        
        public bool IsAlive { get; protected set; }
        public bool UpdateNames { get; set; }
        public bool UpdateValues { get; set; }
        
        public int OutputValues { get; protected set; }
        public List<string> OutputNames { get; private set; }
        
        public DeviceTreeNode RootNode { get; private set; }
        public ToolStripMenuItem RootMenuItem { get; private set; }
        
        protected List<TreeNode> OutputNodes { get; private set; }
        protected List<ToolStripMenuItem> OutputMenuItems { get; private set; }

        public DeviceBase(string address, string name) : base(address, name)
        {

            OutputNames = new List<string>();
            UpdateNames = false;
            UpdateValues = false;

            RootNode = new DeviceTreeNode(this) { Text = String.Format("{0} ({1})", name, address) };
            RootMenuItem = new ToolStripMenuItem(String.Format("{0} ({1})", name, address));
            OutputNodes = new List<TreeNode>();
            OutputMenuItems = new List<ToolStripMenuItem>();
        }

        public virtual void StartRefresh()
        {
            running = true;
            updateThread = new Thread(valueUpdateWorker);
            updateThread.Start();
        }

        public virtual void StopRefresh()
        {
            if (updateThread != null)
            {
                running = false;
                updateThread.Abort();
                //updateThread.Join();
            }
        }

        public void RefreshMenuItems ()
        {
            for (int i = 0; i < OutputNames.Count; i++)
                OutputMenuItems[i].Text = OutputNames[i];
        }

        public void RefreshTreeNodes()
        {
            /* this hides all checkboxes of treenodes which are not outputs */
            HideCheckboxes(RootNode);

            for (int i = 0; i < OutputNames.Count; i++)
                OutputNodes[i].Text = OutputNames[i];
        }

        private void valueUpdateWorker ()
        {
            while (running)
            {
                try
                {
                    Update();

                    /* just pause the polling a few millisecs and decrease the network traffic */
                    Thread.Sleep(500);
                }
                catch (ThreadAbortException)
                {
                    Thread.ResetAbort();
                }
                
            }
        }

        protected virtual void Update()
        {
            /* set output names */
            
            if (RootNode.TreeView != null && RootNode.TreeView.IsHandleCreated)
                RootNode.TreeView.Invoke((MethodInvoker)delegate
                {
                    for (int i = 0; i < OutputNames.Count; i++)
                        if (!OutputNodes[i].Text.Equals(OutputNames[i]))
                        {
                            RootNode.TreeView.SuspendLayout();
                            OutputNodes[i].Text = OutputNames[i];
                            RootNode.TreeView.ResumeLayout();
                        }
                });
                
            if (RootMenuItem.Owner != null && RootMenuItem.Owner.IsHandleCreated)
                RootMenuItem.Owner.Invoke((MethodInvoker)delegate
                {
                    for (int i = 0; i < OutputNames.Count; i++)
                        OutputMenuItems[i].Text = OutputNames[i];
                });

            UpdateNames = false;
            
            /* set output values */
            if (RootNode.TreeView != null && RootNode.TreeView.IsHandleCreated)
                RootNode.TreeView.Invoke((MethodInvoker)delegate
                {
                    for (int i = 0; i < OutputNames.Count; i++)
                        if (!waitForRefresh)
                            if (OutputNodes[i].Checked != (OutputValues & (int)Math.Pow(2, i)) > 0)
                            {
                                RootNode.TreeView.SuspendLayout();
                                OutputNodes[i].Checked = (OutputValues & (int)Math.Pow(2, i)) > 0;
                                RootNode.TreeView.ResumeLayout();
                            }
                });

            if (RootMenuItem.Owner != null && RootMenuItem.Owner.IsHandleCreated)
                RootMenuItem.Owner.Invoke((MethodInvoker)delegate
                {
                    for (int i = 0; i < OutputNames.Count; i++)
                        if (!waitForRefresh)
                            OutputMenuItems[i].Checked = (OutputValues & (int)Math.Pow(2, i)) > 0;
                });

            //if (OnOutputValuesChanged != null)
            //    OnOutputValuesChanged(this, new OutputValuesChangedArgs(this, OutputValues^OldOutputValues, OutputValues));
            
            waitForRefresh = false;
        }

        protected virtual byte[] TcpSendreceive(Byte[] bytes, int port)
        {
            if (bytes == null) return new byte[0];

            // Create a TcpClient.
            // Note, for this client to work you need to have a TcpServer 
            // connected to the same address as specified by the server, port
            // combination.
            TcpClient client = new TcpClientWithTimeout(Address, port, 2000).Connect();
            
            // Get a client stream for reading and writing.
            NetworkStream stream = client.GetStream();

            // Send the message to the connected TcpServer. 
            stream.Write(bytes, 0, bytes.Length);

            // Receive the TcpServer.response.

            // Buffer to store the response bytes.
            Byte[] data = new Byte[256];

            // Read the first batch of the TcpServer response bytes.
            stream.Read(data, 0, data.Length);

            // Close everything.
            stream.Close();
            client.Close();

            IsAlive = true;
            AliveStateChanged();
            
            return data;
        }

        protected virtual string TcpSendreceive(string command, int port)
        {
            if (command == null) return Encoding.UTF8.GetString(new byte[0]);

            // Translate the passed message into ASCII and store it as a Byte array.
            byte[] bytes = Encoding.UTF8.GetBytes(command);
            
            // Create a TcpClient.
            // Note, for this client to work you need to have a TcpServer 
            // connected to the same address as specified by the server, port
            // combination.
            TcpClient client = new TcpClientWithTimeout(Address, port, 2000).Connect();

            // Get a client stream for reading and writing.
            NetworkStream stream = client.GetStream();
            
            // Send the message to the connected TcpServer. 
            stream.Write(bytes, 0, bytes.Length);

            // Receive the TcpServer.response.

            // Buffer to store the response bytes.
            Byte[] data = new Byte[256];
            
            // Read the first batch of the TcpServer response bytes.
            stream.Read(data, 0, data.Length);

            // Close everything.
            stream.Close();
            client.Close();

            IsAlive = true;
            AliveStateChanged();

            return Encoding.UTF8.GetString(data);
        }

        protected void AliveStateChanged()
        {
            try
            {
                if (!IsAlive)
                {
                    OutputValues = 0;

                    if (RootMenuItem != null && RootMenuItem.Owner != null && RootMenuItem.Owner.IsHandleCreated) RootMenuItem.Owner.Invoke((MethodInvoker)(() => DisableChildItems(RootMenuItem)));
                    if (RootNode != null && RootNode.TreeView != null && RootNode.TreeView.IsHandleCreated) RootNode.TreeView.Invoke((MethodInvoker)(() => DisableChildNodes(RootNode)));
                }
                else
                {
                    if (RootMenuItem != null && RootMenuItem.Owner != null && RootMenuItem.Owner.IsHandleCreated) RootMenuItem.Owner.Invoke((MethodInvoker)(() => EnableChildItems(RootMenuItem)));
                    if (RootNode != null && RootNode.TreeView != null && RootNode.TreeView.IsHandleCreated) RootNode.TreeView.Invoke((MethodInvoker)(() => EnableChildNodes(RootNode)));
                }

                if (OnAliveStateChanged != null) OnAliveStateChanged(this, new AliveStateChangedArgs(this, IsAlive));
            }
            catch (Exception)
            {}
        }

        private void EnableChildNodes(TreeNode node)
        {
            node.ForeColor = Color.Black;

            for (int i = 0; i < node.Nodes.Count; i++)
            {
                EnableChildNodes(node.Nodes[i]);
            }
        }

        private void DisableChildNodes(TreeNode node)
        {
            node.ForeColor = Color.Red;

            for (int i = 0; i < node.Nodes.Count; i++)
            {
                DisableChildNodes(node.Nodes[i]);
            }
        }

        private void EnableChildItems(ToolStripMenuItem item)
        {
            item.Enabled = true;

            for (int i = 0; i < item.DropDownItems.Count; i++)
            {
                ToolStripMenuItem child = item.DropDownItems[i] as ToolStripMenuItem;
                if (child != null) EnableChildItems(child);
            }
        }

        private void DisableChildItems(ToolStripMenuItem item)
        {
            item.Enabled = false;

            for (int i = 0; i < item.DropDownItems.Count; i++)
            {
                ToolStripMenuItem child = item.DropDownItems[i] as ToolStripMenuItem;
                if (child != null) DisableChildItems(child);
            }
        }

        public virtual void SetOutput(int outputNr, int value)
        {
            waitForRefresh = true;
        }

        public virtual void SetOutputs(int mask, int value)
        {
            waitForRefresh = true;
        }

        public virtual void SetOutputName(int outputNr, string outputName)
        {
            OutputNames[outputNr] = outputName;
        }
    }
}
